//
// Created by LiuYou on 2021/8/7.
//

#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <getopt.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include "Client.hpp"
#include "../include/Protocol.hpp"


using NetRadio::Client::ClientConfig;
struct ClientConfig clientConfig{
        .rcvPort=(char*) NetRadio::DEFAULT_RCV_PORT,
        .multiGroup=const_cast<char*>(NetRadio::DEFAULT_MULTI_GROUP),
        .playerCmd=const_cast<char*>(NetRadio::DEFAULT_PLAYER_CMD)

};


static void printHelp() {
    printf( "-P --port       指定接受端口\n"
            "-M --mgroup     指定多播组\n"
            "-p ---player    指定播放器\n"
            "-H --help       显示帮助\n" );
}


//static ssize_t writeByte(int fd, uint8_t data[1], size_t len) {
static ssize_t writeByte(int fd, const char* buf, size_t len) {
    int pos = 0;
    while (len > 0) {
        ssize_t ret = write( fd, buf + pos, len );
        if (ret < 0) {
            if (errno == EINTR) {
                continue;
            }
            perror( "write()" );
            return -1;
        }
        len -= ret;
        pos += ret;
    }
    return pos;
}


/// \brief
/// <p> \p
/// -M --mgroup     指定多播组 <p>
/// -P --port       指定接受端口 <p>
/// -p ---player    指定播放器 <p>
/// -H --help       显示帮助 <p>
/// \param argc
/// \param argv
/// \return
int main(int argc, char** argv) {
    // 分析命令行
    /*
     * 初始化
     * 级别: 默认值, 配置文件, 环境变量, 命令行参数
     */
    int index{0};
    struct option argArr[]{{"port",   1, nullptr, 'P'},
                           {"mgroup", 1, nullptr, 'M'},
                           {"player", 1, nullptr, 'p'},
                           {"help",   0, nullptr, 'H'},
                           {nullptr,  0, nullptr, 0}};
    while (true) {
        int ret = getopt_long( argc, argv, "P:M:p:H", argArr, &index );
        if (ret < 0) {
            break;
        }
        switch (ret) {
            case 'P': {
                clientConfig.rcvPort = optarg;
                break;
            }
            case 'M': {
                clientConfig.multiGroup = optarg;
                break;
            }
            case 'p': {
                clientConfig.playerCmd = optarg;
                break;
            }
            case 'H': {
                printHelp();
                exit( 0 );
                break;
            }
            default: {
                printf( "使用方法不正确。" );
                abort();
                break;
            }
        }
    }

    // 设置socket
    int socketFd = socket( PF_INET, SOCK_DGRAM, 0 );
    if (socketFd < 0) {
        perror( "socket()" );
        exit( 1 );
    }



    // 设置socket属性
    struct ip_mreq multiRequest;
    inet_pton( AF_INET, clientConfig.multiGroup, &multiRequest.imr_multiaddr );
    inet_pton( AF_INET, "0.0.0.0", &multiRequest.imr_interface );
    // TODO 注意: !!! 现在man手册中已经没有 imr_ifindex 这个字段了。
//    multiRequest.imr_ifindex = if_nametoindex( "eth0" );
    if (setsockopt( socketFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &multiRequest, sizeof( multiRequest )) < 0) {
        perror( "setsockopt" );
        exit( 1 );
    }

    int optVal = 1;
    if (setsockopt( socketFd, IPPROTO_IP, IP_MULTICAST_LOOP, &optVal, sizeof( optVal )) < 0) {
        perror( "setsockopt()" );
        exit( 1 );
    }

    struct sockaddr_in localAddress{
            .sin_family=AF_INET,
            .sin_port=htons( atoi( clientConfig.rcvPort ))
    };
    inet_pton( AF_INET, "0.0.0.0", &localAddress.sin_addr );
    if (bind( socketFd, (const sockaddr*) &localAddress, sizeof( localAddress )) < 0) {
        perror( "bind()" );
        exit( 1 );
    }


    // 创建管道
    int pipeFd[2];
    if (pipe( pipeFd ) < 0) {
        perror( "pipe()" );
        exit( 1 );
    }


    // 创建子进程
    // 子进程: 负责调用解码器
    /*
     * 父进程: 从网络上收包, 发送给子进程
     * 收节目单
     * 选择频道
     * 收频道包, 发送给子进程
     */
    pid_t pid = fork();
    if (pid < 0) {
        perror( "fork()" );
        exit( 1 );
    }
    // 子进程
    if (pid == 0) {
        close( socketFd );
        close( pipeFd[1] );
        dup2( pipeFd[0], 0 );
        if (pipeFd[0] > 0) {
            close( pipeFd[0] );
        }

        execl( "/bin/sh", "sh", "-c", clientConfig.playerCmd, nullptr );
        perror( "execl()" );
        exit( 1 );
    }

    // 父进程: 从网络上收包, 发送给子进程
    // 收节目单
    using NetRadio::Protocol::MsgProgramList;
    using NetRadio::MSG_PROGRAM_LIST_MAX;
    void* pVoid = malloc( MSG_PROGRAM_LIST_MAX );
    struct MsgProgramList* msgProgramList = static_cast<struct MsgProgramList*>(pVoid);
    if (msgProgramList == nullptr) {
        perror( "malloc()" );
        exit( 1 );
    }

    struct sockaddr_in remoteServerAddress;
    socklen_t remoteServerAddressLen = sizeof( remoteServerAddress );
    ssize_t len;
    while (true) {
        len = recvfrom( socketFd, msgProgramList, MSG_PROGRAM_LIST_MAX, 0,
                        (sockaddr*) &remoteServerAddress, &remoteServerAddressLen );
        if (len < sizeof( struct MsgProgramList )) {
            fprintf( stderr, "message is too small.\n" );
            continue;
        }
        using NetRadio::PROGRAM_LIST_CHANNEL_ID;
        if (msgProgramList->channelId != PROGRAM_LIST_CHANNEL_ID) {
            fprintf( stderr, "channelId is not match!\n" );
            continue;
        }
        break;
    }

    // 打印节目单并选择频道
    using NetRadio::Protocol::MsgProgramListEntry;
    struct MsgProgramListEntry* pos;
    // TODO 注意: !!! 将这里的pos强转为char*, 我不理解 :( 。
    // TODO 注意: !!! 在C/C++中使用C式的强转摸不着头脑 @_@ 。
    for (pos = msgProgramList->entry; (char*) pos < (((char*) msgProgramList) + len);
         pos = static_cast<struct MsgProgramListEntry*>((void*) (((char*) pos) + ntohs( pos->len )))) {
        printf( "channel %d : %s\n", pos->channelId, pos->description );
    }
    free( msgProgramList );

    int chooseId;
    int ret;
    while (ret < 1) {
        ret = scanf( "%d", &chooseId );
        if (ret != 1) {
            exit( 1 );
        }
    }

    // 收频道包, 发送给子进程
    fprintf( stdout, "chooseId = %d\n", ret );

    using NetRadio::Protocol::MsgChannel;
    using NetRadio::MSG_CHANNEL_MAX;
    void* pVoid1 = malloc( MSG_CHANNEL_MAX );
    struct MsgChannel* msgChannel = static_cast<struct MsgChannel*>(pVoid1);
    if (msgChannel == nullptr) {
        perror( "malloc()" );
        exit( 1 );
    }

    struct sockaddr_in remoteAddress;
    socklen_t remoteAddressLen = sizeof( remoteAddress );
    while (true) {
        ssize_t len = recvfrom( socketFd, msgChannel, MSG_CHANNEL_MAX, 0,
                                (sockaddr*) &remoteAddress, &remoteAddressLen );
        if (remoteAddress.sin_addr.s_addr != remoteServerAddress.sin_addr.s_addr ||
            remoteAddress.sin_port != remoteServerAddress.sin_port) {
            fprintf( stderr, "Ignore: address not match.\n" );
            continue;
        }
        if (len < sizeof( struct MsgChannel )) {
            fprintf( stderr, "Ignore: message too small.\n" );
            continue;
        }
        if (msgChannel->channelId == chooseId) {
            fprintf( stdout, "accepted msg: %d receive.\n", msgChannel->channelId );
            using NetRadio::ChannelId;
            if (writeByte( pipeFd[1], (char*) msgChannel->data, len - sizeof( ChannelId )) < 0) {
                exit( 1 );
            }
        }
    }

    // 写一个信号处理函数。
    free( msgChannel );
    close( socketFd );

    exit( 0 );
}